/*
* Sun Public License Notice
*
* The contents of this file are subject to the Sun Public License
* Version 1.0 (the "License"). You may not use this file except in
* compliance with the License. A copy of the License is available at
* http://www.sun.com/
*
* The Original Code is Forte for Java, Community Edition. The Initial
* Developer of the Original Code is Sun Microsystems, Inc. Portions
* Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved.
*/
package org.openide.util.datatransfer;
import java.awt.datatransfer.*;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import javax.swing.event.EventListenerList;
import org.openide.util.NbBundle;
/** Provides additional operations on
* a transferable.
*
* @author Jaroslav Tulach
*/
public class ExTransferable extends Object implements Transferable {
/** An implementation of <code>Transferable</code> that contains no data. */
public static final Transferable EMPTY = new Empty ();
/** Flavor for transfer of multiple objects.
*/
public static final DataFlavor multiFlavor=
new DataFlavor (
"application/x-java-openide-multinode;class=org.openide.util.datatransfer.MultiTransferObject", // NOI18N
NbBundle.getBundle (ExTransferable.class).
getString ("transferFlavorsMultiFlavorName")
);
/** hash map that assigns objects to dataflavors (DataFlavor, Single)
* @associates Single*/
private HashMap map;
/** listeners */
private EventListenerList listeners;
/** Creates new support.
* @param t transferable to to copy values from
* @param o clipobard owner (or null)
*/
private ExTransferable (final Transferable t) {
map = new HashMap ();
final DataFlavor[] df = t.getTransferDataFlavors ();
if (df != null) {
for (int i = 0; i < df.length; i++) {
try {
final int fi = i;
map.put (df[i], new Single (df[i]) {
public Object getData () throws IOException, UnsupportedFlavorException {
return t.getTransferData (df[fi]);
}
});
} catch (Exception ex) {
// ignore if the data cannot be retrived
}
}
}
}
/** Add a new flavor with its data.
* @param single the single transferable to use
*/
public void put (Single single) {
map.put (single.flavor, single);
}
/** Remove a flavor from the supported set.
* @param flavor the flavor to remove
*/
public void remove (DataFlavor flavor) {
map.remove (flavor);
}
// XXX ???
// @return array with <CODE>contextFlavor</CODE>
// @see TransferFlavors.contextFlavor
/* Get supported flavors.
* @return the flavors
*/
public DataFlavor[] getTransferDataFlavors () {
return (DataFlavor[])map.keySet ().toArray (new DataFlavor[0]);
}
/* Is this flavor supported?
* @param flavor flavor to test
* @return <code>true</code> if this flavor is supported
*/
public boolean isDataFlavorSupported (DataFlavor flavor) {
return map.containsKey (flavor);
}
/* Get the transferable data for this flavor.
* @param flavor the flavor
* @return the data
* @throws IOException currently not thrown
* @throws UnsupportedFlavorException if that flavor is not supported
*/
public Object getTransferData (DataFlavor flavor)
throws UnsupportedFlavorException, IOException {
Single o = (Single) map.get (flavor);
if (o == null)
throw new UnsupportedFlavorException (flavor);
return o.getTransferData(flavor);
}
/** Method to create a new extended transferable from a plain transferable.
* If the given transferable is already <code>ExTransferable</code>, then it
* is returned as is.
* Otherwise the data is copied.
*
* @param t transferable to create support for
* @return extended transferable
*/
public static ExTransferable create (Transferable t) {
if (t instanceof ExTransferable) return (ExTransferable)t;
return new ExTransferable (t);
}
/** Adds a listener to watch the life-cycle of this object.
*
* @param l the listener
*/
public synchronized final void addTransferListener (TransferListener l) {
if (listeners == null) {
listeners = new EventListenerList ();
}
listeners.add (TransferListener.class, l);
}
/** Removes a listener.
*/
public synchronized final void removeTransferListener (TransferListener l) {
if (listeners != null) {
listeners.remove (TransferListener.class, l);
}
}
/** Fires notification to all listeners about
* accepting the drag.
* @param action one of java.awt.dnd.DnDConstants.ACTION_*
*/
final void fireAccepted (int action) {
if (listeners == null) {
return;
}
Object[] arr = listeners.getListenerList ();
for (int i = arr.length - 1; i >= 0; i -= 2) {
((TransferListener)arr[i]).accepted (action);
}
}
/** Fires notification to all listeners about
* accepting the drag.
*/
final void fireRejected () {
if (listeners == null) {
return;
}
Object[] arr = listeners.getListenerList ();
for (int i = arr.length - 1; i >= 0; i -= 2) {
((TransferListener)arr[i]).rejected ();
}
}
/** Fires notification to all listeners about
* accepting the drag.
*/
final void fireOwnershipLost () {
if (listeners == null) {
return;
}
Object[] arr = listeners.getListenerList ();
for (int i = arr.length - 1; i >= 0; i -= 2) {
((TransferListener)arr[i]).ownershipLost ();
}
}
/** Support for transferable owner with only one data flavor.
* Subclasses need only implement {@link #getData}.
*/
public static abstract class Single extends Object implements Transferable {
/** the supported data flavor */
private DataFlavor flavor;
/** Constructor.
* @param flavor flavor of the data
*/
public Single (DataFlavor flavor) {
this.flavor = flavor;
}
/* Flavors that are supported.
* @return array with <CODE>contextFlavor</CODE>
* @see TransferFlavors.contextFlavor
*/
public DataFlavor[] getTransferDataFlavors () {
return new DataFlavor[] { flavor };
}
/* Is the flavor supported?
* @param flavor flavor to test
* @return true if this flavor is supported
*/
public boolean isDataFlavorSupported (DataFlavor flavor) {
return this.flavor.equals (flavor);
}
/* Creates transferable data for this flavor.
*/
public Object getTransferData (DataFlavor flavor)
throws UnsupportedFlavorException, IOException {
if (!this.flavor.equals (flavor)) throw new UnsupportedFlavorException (flavor);
return getData ();
}
/** Abstract method to override to provide the right data for this
* transferable.
*
* @return the data
* @throws IOException when an I/O error occurs
* @throws UnsupportedFlavorException if the flavor is not supported
*/
protected abstract Object getData () throws IOException, UnsupportedFlavorException;
}
/** Transferable object for multiple transfer.
* It allows several types of data
* to be combined into one clipboard element.
*
* @author Jaroslav Tulach
*/
public static class Multi extends Object implements Transferable {
/** object that is about to be return as result of transfer */
private MultiTransferObject transferObject;
/** supported flavors list */
private static final DataFlavor[] flavorList = { multiFlavor };
/** Constructor taking a list of <code>Transferable</code> objects, but no clipboard owner.
* No one will be notified when ownership of the clipboard is lost.
*
* @param trans array of transferable objects
*/
public Multi (Transferable[] trans) {
transferObject = new TransferObjectImpl (trans);
}
/** Get supported flavors.
* @return only one flavor, {@link #multiFlavor}
*/
public DataFlavor[] getTransferDataFlavors() {
return flavorList;
}
/** Is this flavor supported?
* @param flavor the flavor
* @return <code>true</code> only if the flavor is {@link #multiFlavor}
*/
public boolean isDataFlavorSupported(DataFlavor flavor) {
return flavor.equals (multiFlavor);
}
/** Get transfer data.
* @param flavor the flavor ({@link #multiFlavor})
* @return {@link MultiTransferObject} that represents data in this object
* @exception UnsupportedFlavorException when the flavor is not supported
* @exception IOException when it is not possible to read data
*/
public Object getTransferData(DataFlavor flavor)
throws UnsupportedFlavorException, IOException {
if (!isDataFlavorSupported (flavor)) {
throw new UnsupportedFlavorException (flavor);
}
return transferObject;
}
/** Class implementing MultiTransferObject interface. */
static class TransferObjectImpl implements MultiTransferObject {
/** transferable objects */
private Transferable[] trans;
/** Creates new object from transferable objects.
* @param trans array of transferable objects
*/
public TransferObjectImpl (Transferable[] trans) {
this.trans = trans;
}
/** Number of transfered elements.
* @return number of elements
*/
public int getCount () {
return trans.length;
}
/** @return Transferable at the specific index */
public Transferable getTransferableAt(int index) {
return trans[index];
}
/** Test whether data flavor is supported by index-th item.
*
* @param index the index of
* @param flavor flavor to test
* @return <CODE>true</CODE> if flavor is supported by all elements
*/
public boolean isDataFlavorSupported(int index, DataFlavor flavor) {
try {
return trans[index].isDataFlavorSupported (flavor);
} catch (Exception e) {
return false;
// patch to get the Netbeans start under Solaris
// [PENDINGbeta]
}
}
/** Test whether each transfered item supports at least one of these
* flavors. Each item can support different flavor.
* @param array array of flavors
*/
public boolean areDataFlavorsSupported (DataFlavor[] array) {
HashSet flav = new HashSet ();
for (int i = 0; i < array.length; i++) {
flav.add (array[i]);
}
// cycles through all transferable objects and scans their content
// to find out if each supports at least one requested flavor
outer: for (int i = 0; i < trans.length; i++) {
// insert all flavors of the first object into array
DataFlavor[] flavors = trans[i].getTransferDataFlavors ();
if (flavors == null) return false;
// loop through rest of Transferable objects
for (int j = 0; j < flavors.length; j++) {
if (flav.contains (flavors[j])) {
// this flavor is supported
continue outer;
}
}
// for this transferable no flavor is supported
return false;
}
return true;
}
/** Gets list of all supported flavors for i-th element.
* @param i the element to find flavors for
* @return array of supported flavors
*/
public DataFlavor[] getTransferDataFlavors (int i) {
return trans[i].getTransferDataFlavors ();
}
/**
* @param indx index of element to work with
* @param flavor one needs to obtain
* @return object for the flavor of the i-th element
*/
public Object getTransferData(int indx, DataFlavor flavor)
throws UnsupportedFlavorException, IOException {
return trans[indx].getTransferData (flavor);
}
/** Compute common flavors.
* @param t array of transferable objects
* @return array of common flavors
*/
private static DataFlavor[] computeCommonFlavors (Transferable[] t) {
if (t.length == 0) {
// no flavor is supported => return empty array
return new DataFlavor[] { };
}
// insert all flavors of the first object into array
DataFlavor[] flavors = t[0].getTransferDataFlavors ();
// number of non null elements in flavors array
int flavorsCount = (flavors == null)? 0 : flavors.length;
int flavorsLength = flavorsCount; // non-changing length of the original flavors array
// loop through rest of Transferable objects
for (int i = 1; i < t.length; i++) {
// loop through array
for (int j = 0; j < flavorsLength; j++) {
// if the flavor is not supported
boolean supported = false;
try {
supported = t[i].isDataFlavorSupported (flavors[j]);
} catch (Exception e) {
// patch to get the Netbeans start under Solaris
// [PENDINGbeta]
}
if (flavors[j] != null && !supported) {
// then clear it
flavors[j] = null;
flavorsCount--;
}
}
}
// create resulting array
DataFlavor[] result = new DataFlavor[flavorsLength];
for (int i = 0, j = 0; i < flavorsLength; i++) {
if (flavors[i] != null) {
// add it to the result
result[j++] = flavors[i];
}
}
return result;
}
}
}
/** TransferableOwnerEmpty is TransferableOwner that contains no data.
*
* @author Jaroslav Tulach
*/
private static class Empty extends Object implements Transferable {
/** Package private constructor to allow only one instance from TransferableOwner.
*/
Empty() {
}
/** Flavors that are supported.
* @return empty array
*/
public DataFlavor[] getTransferDataFlavors () {
return new DataFlavor[] {};
}
/** Does not support any flavor
* @param flavor flavor to test
* @return false
*/
public boolean isDataFlavorSupported (DataFlavor flavor) {
return false;
}
/** Creates transferable data for this flavor.
* @exception UnsupportedFlavorException does not support any flavor
*/
public Object getTransferData (DataFlavor flavor)
throws UnsupportedFlavorException, IOException {
throw new UnsupportedFlavorException (flavor);
}
/** Does nothing.
* @param clipboard the clipboard
* @param contents the content of clipboard
*/
public void lostOwnership (Clipboard clipboard, Transferable contents) {
}
}
}
/*
* Log
* 13 src-jtulach1.12 1/12/00 Pavel Buzek I18N
* 12 src-jtulach1.11 1/12/00 Jesse Glick MIME types ought not be
* in bundles.
* 11 src-jtulach1.10 10/22/99 Ian Formanek NO SEMANTIC CHANGE - Sun
* Microsystems Copyright in File Comment
* 10 src-jtulach1.9 7/3/99 Ian Formanek Survives when
* Transferable.getTransferDataFlavors returns null
* 9 src-jtulach1.8 7/1/99 Petr Hamernik bugfixes
* 8 src-jtulach1.7 6/30/99 Jaroslav Tulach Drag and drop support
* 7 src-jtulach1.6 6/8/99 Ian Formanek ---- Package Change To
* org.openide ----
* 6 src-jtulach1.5 4/30/99 David Simonek
* 5 src-jtulach1.4 4/28/99 David Simonek changed multiFlavor to
* work correctly with DnD
* 4 src-jtulach1.3 3/11/99 Jesse Glick multiFlavor now "final".
* 3 src-jtulach1.2 3/10/99 Jesse Glick [JavaDoc]
* 2 src-jtulach1.1 2/25/99 Jaroslav Tulach Change of clipboard
* management
* 1 src-jtulach1.0 2/25/99 Jaroslav Tulach
* $
* Beta Change History:
* 0 Tuborg 0.11 --/--/98 Jaroslav Tulach Moved to package org.openide.util.datatransfer
* 0 Tuborg 0.12 --/--/98 Ales Novak NoOwner added
* 0 Tuborg 0.13 --/--/98 Jaroslav Tulach Filter added
* 0 Tuborg 0.15 --/--/98 Jan Jancura It's possible to add some flavors to the Filter TransfOwner
*/